package com.dong.aoptools.cast.method;

import com.dong.aoptools.constant.PropertiesConst;
import com.dong.aoptools.util.PathUtils;
import com.dong.aoptools.util.PropertiesUtils;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AdviceAdapter;

/**
 * @description:
 * @author: 陈瑞东
 * @create: 2021-11-12 11:48
 */
public class MethodAOPVisitor extends AdviceAdapter {

    private boolean isAOP = false; // 该类是否需要代理
    private int returnLocal; // 返回值的存储位置
    private int aopObjLocal; // 代理类对象的存储的位置
    private String aopFullClass; // 代理类的全类名
    private String aopAnno; // 扫描包含该注解的类

    public MethodAOPVisitor(int api, MethodVisitor mv, int access, String name, String descriptor) {
        super(api, mv, access, name, descriptor);
        // 获取包名
        String pkg = PropertiesUtils.getConfigProperty(PropertiesConst.CONFIG_FIELD_PACKAGE, "aoptools");
        // 转换包名
        pkg = PathUtils.point2Slash(pkg);
        // 获取类名
        String className = PropertiesUtils.getConfigProperty(PropertiesConst.CONFIG_FIELD_CLASS, "AopTools");
        // 赋值
        this.aopFullClass = pkg + "/" + className;
        // 获取注解
        String anno = PropertiesUtils.getConfigProperty(PropertiesConst.CONFIG_FIELD_ANNO, "AopToolsAnno");
        // 赋值
        this.aopAnno = anno;
    }

    @Override
    public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
        // 有@AOPDemoAnno的方法执行转换
        if (descriptor.contains(aopAnno)) {
            isAOP = true;
        }
        return mv.visitAnnotation(descriptor, visible);
    }

    @Override
    protected void onMethodEnter() {
        if (!isAOP) {
            return;
        }
        // 为变量分配空间
        aopObjLocal = newLocal(Type.getType(Object.class));
        returnLocal = newLocal(Type.getType(Object.class));

        // 创建代理类对象
        mv.visitTypeInsn(NEW, aopFullClass);
        // 赋值代理类对象给准备好的空间
        mv.visitVarInsn(ASTORE, aopObjLocal);
        // 加载代理类对象入操作栈
        mv.visitVarInsn(ALOAD, aopObjLocal);
        // 调用构造方法，初始化代理类对象
        mv.visitMethodInsn(INVOKESPECIAL, aopFullClass, "<init>", "()V", false);

        mv.visitVarInsn(ALOAD, aopObjLocal);
        // 调用代理类对象的 before 方法
        mv.visitMethodInsn(INVOKEVIRTUAL, aopFullClass, PropertiesUtils.getConfigProperty(PropertiesConst.CONFIG_FIELD_BEFORE, "before"), "()V", false);
    }

    @Override
    protected void onMethodExit(int opcode) {
        if (!isAOP) {
            return;
        }
        // 将所有基本类型转成引用类型
        if (opcode != RETURN) {
            switch (opcode) {
                case IRETURN:
                    mv.visitInsn(DUP);
                    mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
                    break;
                case FRETURN:
                    mv.visitInsn(DUP);
                    mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
                    break;
                case ARETURN:
                    mv.visitInsn(DUP);
                    break;
                case LRETURN:
                    mv.visitInsn(DUP2);
                    mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
                    break;
                case DRETURN:
                    mv.visitInsn(DUP2);
                    mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
                    break;
            }
            mv.visitVarInsn(ASTORE, returnLocal);
        } else {
            mv.visitInsn(ACONST_NULL);
            mv.visitVarInsn(ASTORE, returnLocal);
        }

        // 设置变量调用方法
        mv.visitVarInsn(ALOAD, aopObjLocal);
        mv.visitVarInsn(ALOAD, returnLocal);
        mv.visitMethodInsn(INVOKEVIRTUAL, aopFullClass, PropertiesUtils.getConfigProperty(PropertiesConst.CONFIG_FIELD_AFTER, "after"), "(Ljava/lang/Object;)V", false);
    }
}
